<!DOCTYPE html>

<html>

<head>
    <meta charset="utf-8">
    <title>取点工具</title>
    <style>
        * {
            box-sizing: border-box;
        }

        .bottombar {
            padding: 8px;
        }

        html {
            height: 100%;
        }
            /*body {
                margin: 0;
                display: flex;
                flex-direction: column;
                height: 100%;
            }
            .upperpanel {
                flex-grow: 1;
                overflow: auto;
                padding: 8px;
            }*/
        .upperpanel {
            user-select: none;
        }

        .line {
            position: absolute;
            opacity: 0.75;
            overflow: visible;
            transition: opacity 0.16s ease-out, width 0.16s ease-out, height 0.16s ease-out;
        }

        .line:hover {
            opacity: 1;
        }

        .vline {
            width: 1px;
            top: 0;
            left: 0;
            bottom: 0;
            background: #ffff00;
            cursor: col-resize;
        }
        .vline:hover {
            width: 1.5px;
        }

        .hline {
            height: 1px;
            left: 0;
            right: 0;
            top: 0;
            background: #00ffff;
            cursor: row-resize;
        }
        .hline:hover {
            height: 1.5px;
        }
        .imgbox {
            position: relative;
            width: -moz-fit-content;
            width: fit-content;
        }


        #imgbox {
            --crop-left: 0px;
            --crop-top: 0px;
            --crop-right: 40px;
            --crop-bottom: 40px;
            --crop-width: 40px;
            --crop-height: 40px;
        }

        #imgelt {
            image-rendering: optimizespeed;
            image-rendering: crisp-edges;
            image-rendering: pixelated;
        }

        #croparea {
            position: absolute;
            left: 0;
            top: 0;
            width: calc(var(--crop-right) - var(--crop-left));
            height: calc(var(--crop-bottom) - var(--crop-top));
            cursor: grab;
            /* border: solid 1px #ffff00; */
            transform: translate(var(--crop-left, 0px), var(--crop-top, 0px));
        }

        .dragging {
            cursor: grabbing;
        }

        .dragger {
            position: absolute;
            width: 6px;
            height: 6px;
            background-color: rgba(255, 255, 0, 0.6);
        }

        .dragger-fullwidth {
            width: 100%;
        }

        .dragger-fullheight {
            height: 100%;
        }

        .dragger-left {
            left: -6px;
            cursor: e-resize;
        }

        .dragger-right {
            left: 100%;
            cursor: w-resize;
        }

        .dragger-top {
            top: -6px;
            cursor: n-resize;
        }

        .dragger-bottom {
            top: 100%;
            cursor: s-resize;
        }

        .dragger-left.dragger-top {
            cursor: nw-resize;
        }

        .dragger-right.dragger-top {
            cursor: ne-resize;
        }

        .dragger-left.dragger-bottom {
            cursor: sw-resize;
        }

        .dragger-right.dragger-bottom {
            cursor: se-resize;
        }
    </style>
</head>

<body>
    <div class="bottombar">
        <input type="file" id="filechooser" accept="image/*" />
        <select id="modesel">
            <option value="abs">绝对坐标</option>
            <option value="vwvh">宽高相对坐标</option>
            <option value="leftvh">高度相对坐标（左对齐）</option>
            <option value="midvh">高度相对坐标（居中）</option>
            <option value="rightvh">高度相对坐标（右对齐）</option>
        </select>
        <button id="cropbtn">crop</button>
        <button id="resetbtn">reset</button>
        <pre>(<span id="leftpos">0</span>, <span id="toppos">0</span>, <span id="rightpos">0</span>, <span id="bottompos">0</span>)</pre>
    </div>
    <div class="upperpanel">
        <div id="imgbox" class="imgbox">
            <img id="imgelt" src="" draggable="false" crossorigin="anonymous" />
            <div id="croparea">
                <div id="drag-l" class="dragger dragger-left dragger-fullheight"></div>
                <div id="drag-t" class="dragger dragger-top dragger-fullwidth"></div>
                <div id="drag-r" class="dragger dragger-right dragger-fullheight"></div>
                <div id="drag-b" class="dragger dragger-bottom dragger-fullwidth"></div>
                <div id="drag-tl" class="dragger dragger-top dragger-left"></div>
                <div id="drag-tr" class="dragger dragger-top dragger-right"></div>
                <div id="drag-bl" class="dragger dragger-bottom dragger-left"></div>
                <div id="drag-br" class="dragger dragger-bottom dragger-right"></div>
            </div>
        </div>
    </div>
    <img id="cropimg" src="" crossorigin="anonymous"/>
    <p>Save ROI template and metadata to: <button id='dirbtn'>Browse...</button> <span id="savedir">/dev/null</span>
        <br><input type="text" id="savebasename" placeholder="basename for load_roi"> <button id="savebtn">save image</button></p>
    <script>
        let $ = (x) => document.querySelector(x)
        let $$ = (x) => document.querySelectorAll(x)
        let changefile = function () { }
        $('#filechooser').addEventListener('filechanged', function () {
            changefile()
        })

        let absleft = 0, abstop = 0, absright = 40, absbottom = 40

        //              [vw, vh, 1]
        let leftmat = [0, 0, 0]
        let rightmat = [0, 0, 40]
        let topmat = [0, 0, 0]
        let bottommat = [0, 0, 40]

        let format = function (base, num, unit) {
            if (num == 0) return base === '' ? '0' : base
            if (num > 0) return base === '' ? num.toFixed(3) + '*' + unit : base + '+' + num.toFixed(3) + '*' + unit
            if (num < 0) return base + num.toFixed(3) + '*' + unit
        }

        function updateOutput() {
            let imgelt = $('#imgelt')
            let vwidth = imgelt.width
            let vheight = imgelt.height

            // let hline1top = $('#hline1').getBoundingClientRect().top - imgelt.getBoundingClientRect().top
            // let hline2top = $('#hline2').getBoundingClientRect().top - imgelt.getBoundingClientRect().top;
            // [abstop, absbottom] = [hline1top, hline2top].sort((x, y) => x - y)

            // let vline1left = $('#vline1').getBoundingClientRect().left - imgelt.getBoundingClientRect().left
            // let vline2left = $('#vline2').getBoundingClientRect().left - imgelt.getBoundingClientRect().left;
            // [absleft, absright] = [vline1left, vline2left].sort((x, y) => x - y)

            let mode = $('#modesel').selectedOptions[0].value
            if (mode === 'abs') {
                $('#leftpos').innerText = absleft.toString()
                $('#toppos').innerText = abstop.toString()
                $('#rightpos').innerText = absright.toString()
                $('#bottompos').innerText = absbottom.toString()
                leftmat = [0, 0, absleft]
                rightmat = [0, 0, absright]
                topmat = [0, 0, abstop]
                bottommat = [0, 0, absbottom]
                return
            }
            let topvh = abstop / vheight * 100
            let bottomvh = absbottom / vheight * 100
            $('#toppos').innerText = format('', topvh, 'vh')
            $('#bottompos').innerText = format('', bottomvh, 'vh')
            topmat = [0, topvh, 0]
            bottommat = [0, bottomvh, 0]

            switch (mode) {
                case 'vwvh': {
                    let leftvw = absleft / vwidth * 100
                    let rightvw = absright / vwidth * 100
                    $('#leftpos').innerText = format('', leftvw, 'vw')
                    $('#rightpos').innerText = format('', rightvw, 'vw')
                    leftmat = [leftvw, 0, 0]
                    rightmat = [rightvw, 0, 0]
                    break
                }
                case 'leftvh': {
                    let leftvh = absleft / vheight * 100
                    let rightvh = absright / vheight * 100
                    $('#leftpos').innerText = format('', leftvh, 'vh')
                    $('#rightpos').innerText = format('', rightvh, 'vh')
                    leftmat = [0, leftvh, 0]
                    rightmat = [0, rightvh, 0]
                    break
                }
                case 'midvh': {
                    let leftvh = (absleft - vwidth / 2) / vheight * 100
                    let rightvh = (absright - vwidth / 2) / vheight * 100

                    $('#leftpos').innerText = format('50*vw', leftvh, 'vh')
                    $('#rightpos').innerText = format('50*vw', rightvh, 'vh')
                    leftmat = [50, leftvh, 0]
                    rightmat = [50, rightvh, 0]
                    break
                }
                case 'rightvh': {
                    let leftvh = (absleft - vwidth) / vheight * 100
                    let rightvh = (absright - vwidth) / vheight * 100

                    $('#leftpos').innerText = format('100*vw', leftvh, 'vh')
                    $('#rightpos').innerText = format('100*vw', rightvh, 'vh')
                    leftmat = [100, leftvh, 0]
                    rightmat = [100, rightvh, 0]
                    break
                }
            }
        }
        const imgbox = $('#imgbox')
        const imgelt = $('#imgelt')

        function updateLayout() {
            let vw = imgelt.width / 100
            let vh = imgelt.height / 100

            let mat3mul = function (mat1, mat2) {
                return mat1[0] * mat2[0] + mat1[1] * mat2[1] + mat1[2] * mat2[2]
            }
            absleft = mat3mul(leftmat, [vw, vh, 1])
            absright = mat3mul(rightmat, [vw, vh, 1])
            abstop = mat3mul(topmat, [vw, vh, 1])
            absbottom = mat3mul(bottommat, [vw, vh, 1])
            imgbox.style.setProperty('--crop-left', absleft.toString() + 'px');
            imgbox.style.setProperty('--crop-right', absright.toString() + 'px');
            imgbox.style.setProperty('--crop-top', abstop.toString() + 'px');
            imgbox.style.setProperty('--crop-bottom', absbottom.toString() + 'px');
            // setX($('#vline1'), mat3mul(leftmat, [vw, vh, 1]))
            // setX($('#vline2'), mat3mul(rightmat, [vw, vh, 1]))
            // setY($('#hline1'), mat3mul(topmat, [vw, vh, 1]))
            // setY($('#hline2'), mat3mul(bottommat, [vw, vh, 1]))
            updateOutput()
        }

        function updateView() {
            imgbox.style.setProperty('--crop-left', absleft.toString() + 'px');
            imgbox.style.setProperty('--crop-right', absright.toString() + 'px');
            imgbox.style.setProperty('--crop-top', abstop.toString() + 'px');
            imgbox.style.setProperty('--crop-bottom', absbottom.toString() + 'px');
        }

        let setLeft = function(value) {
            if (value >= absright) {
                value = absright - 1
            }
            if (value < 0) {
                value = 0
            }
            absleft = value
            updateView()
            updateOutput()
        }
        let setRight = function(value) {
            if (value <= absleft) {
                value = absleft + 1
            }
            if (value > imgelt.width) {
                value = imgelt.width
            }
            absright = value
            updateView()
            updateOutput()
        }
        let setTop = function(value) {
            if (value >= absbottom) {
                value = absbottom - 1
            }
            if (value < 0) {
                value = 0
            }
            abstop = value
            updateView()
            updateOutput()
        }
        let setBottom = function(value) {
            if (value <= abstop) {
                value = abstop + 1
            }
            if (value > imgelt.height) {
                value = imgelt.height
            }
            absbottom = value
            updateView()
            updateOutput()
        }

        let fixedX, fixedY
        function setFixedX(value) {
            fixedX = value
        }
        function setMovingX(value) {
            if (value < fixedX) {
                [absleft, absright] = [value, fixedX]
            } else {
                [absleft, absright] = [fixedX, value]
            }
            if (absleft < 0) absleft = 0;
            if (absright > imgelt.width) absright = imgelt.width;
            updateView()
            updateOutput()
        }

        function setFixedY(value) {
            fixedY = value
        }
        function setMovingY(value) {
            if (value < fixedY) {
                [abstop, absbottom] = [value, fixedY]
            } else {
                [abstop, absbottom] = [fixedY, value]
            }
            if (abstop < 0) abstop = 0;
            if (absbottom > imgelt.height) absbottom = imgelt.height;
            updateView()
            updateOutput()
        }

        let addDragHandler = function(elt, handler) {
            elt.addEventListener('mousedown', function (e) {
                e.preventDefault()
                e.stopPropagation()
                this.draginitX = e.pageX
                this.draginitY = e.pageY
                let elm = elt
                handler.enter?.call(handler, e)
                let originaltranslateX = handler.getX()
                let originaltranslateY = handler.getY()
                if (isNaN(originaltranslateX)) originaltranslateX = 0
                let movehandler = function (e) {
                    e.preventDefault()
                    let offsetX = e.pageX - elm.draginitX
                    let eltX = originaltranslateX + offsetX
                    let offsetY = e.pageY - elm.draginitY
                    let eltY = originaltranslateY + offsetY
                    // elm.style.transform = 'translateX(' + eltX.toString() + ')'
                    // document.documentElement.style.setProperty(prop, eltX)
                    handler.setX(eltX)
                    handler.setY(eltY)
                    updateLayout()
                }
                document.addEventListener('mousemove', movehandler)
                let uphandler = function (e) {
                    e.preventDefault()
                    handler.leave?.call(handler, e)
                    document.removeEventListener('mousemove', movehandler)
                    document.removeEventListener('mouseup', uphandler)
                }
                document.addEventListener('mouseup', uphandler)
            })
        }

        let clamp = function (value, lower, upper) {
            if (value < lower) return lower;
            if (value > upper) return upper;
            return value;
        }

        addDragHandler($("#drag-l"), {enter(){ setFixedX(absright) }, getX: ()=>absleft, getY: ()=>0, setX: setMovingX, setY: (value)=>null})
        addDragHandler($("#drag-r"), {enter(){ setFixedX(absleft) }, getX: ()=>absright, getY: ()=>0, setX: setMovingX, setY: (value)=>null})
        addDragHandler($("#drag-t"), {enter(){ setFixedY(absbottom) }, getX: ()=>0, getY: ()=>abstop, setX: (value)=>null, setY: setMovingY})
        addDragHandler($("#drag-b"), {enter(){ setFixedY(abstop) }, getX: ()=>0, getY: ()=>absbottom, setX: (value)=>null, setY: setMovingY})
        addDragHandler($("#drag-tl"), {enter(){ setFixedX(absright); setFixedY(absbottom) }, getX: ()=>absleft, getY: ()=>abstop, setX: setMovingX, setY: setMovingY})
        addDragHandler($("#drag-tr"), {enter(){ setFixedX(absleft); setFixedY(absbottom) }, getX: ()=>absright, getY: ()=>abstop, setX: setMovingX, setY: setMovingY})
        addDragHandler($("#drag-bl"), {enter(){ setFixedX(absright); setFixedY(abstop) }, getX: ()=>absleft, getY: ()=>absbottom, setX: setMovingX, setY: setMovingY})
        addDragHandler($("#drag-br"), {enter(){ setFixedX(absleft); setFixedY(abstop) }, getX: ()=>absright, getY: ()=>absbottom, setX: setMovingX, setY: setMovingY})
        addDragHandler($("#croparea"), {
            enter(e) {
                this.width = absright - absleft
                this.height = absbottom - abstop
            },
            getX: ()=>absleft,
            getY: ()=>abstop,
            setX(value) {
                absleft = clamp(value, 0, imgelt.width - this.width)
                absright = clamp(value + this.width, this.width, imgelt.width)
                updateView()
                updateOutput()
            },
            setY(value) {
                abstop = clamp(value, 0, imgelt.height - this.height)
                absbottom = clamp(value + this.height, this.height, imgelt.height)
                updateView()
                updateOutput()
            }
        })
        addDragHandler(imgelt, {
            enter(e) {
                let rect = imgelt.getBoundingClientRect()
                let scrollLeft = window.pageXOffset || document.documentElement.scrollLeft
                let scrollTop = window.pageYOffset || document.documentElement.scrollTop
                absleft = e.pageX - (rect.left + scrollLeft)
                abstop = e.pageY - (rect.top + scrollTop)
                absright = absleft
                absbottom = abstop
                setFixedX(absleft)
                setFixedY(abstop)
                updateView()
                updateOutput()
            },
            getX: ()=>absleft,
            getY: ()=>abstop,
            setX(value) {
                setMovingX(value)
            },
            setY(value) {
                setMovingY(value)
            }
        })


        $('#imgelt').addEventListener('load', () => {
            updateLayout()
        })
        $('#modesel').addEventListener('change', () => updateOutput())

        let loadImage = function(file) {
            if($('#imgelt').src) {
                URL.revokeObjectURL($('#imgelt').src)
            }
            $('#imgelt').src = URL.createObjectURL(file)
        }

        $('#filechooser').addEventListener('change', function(e) {
            loadImage(e.target.files[0])
        })


        let createCanvas = function(w, h) {
            let canvas = document.createElement('canvas')
            canvas.width = w
            canvas.height = h
            return canvas
        }
        let offscreenCanvasToBlob = function(c) {
            return new Promise((resolve, reject)=>{
                c.toBlob(resolve)
            })
        }

        let imageBlob, roimetadata
        $('#cropbtn').addEventListener('click', async function(){
            let width = absright - absleft
            let height = absbottom - abstop
            let canvas = createCanvas(width, height)
            let ctx = canvas.getContext('2d')
            ctx.drawImage($('#imgelt'), absleft, abstop, width, height, 0, 0, width, height)
            // let blob = await offscreenCanvasToBlob(canvas)
            // if($('#cropimg').src) {
            //     URL.revokeObjectURL($('#cropimg').src)
            // }
            roimetadata = JSON.stringify({
                bbox_matrix: [leftmat, topmat, rightmat, bottommat],
                native_resolution: [imgelt.width, imgelt.height]
            })
            canvas.toBlob(blob=>{
                imageBlob = blob
                if($('#cropimg').src) {
                    URL.revokeObjectURL($('#cropimg').src)
                }
                $('#cropimg').src = URL.createObjectURL(blob)
                $('#cropimg').scrollIntoView(true)
            })
        })

        $('#resetbtn').addEventListener('click', function(){
            leftmat = [0, 0, 0]
            rightmat = [0, 0, 40]
            topmat = [0, 0, 0]
            bottommat = [0, 0, 40]
            updateLayout();
            updateOutput();
        })

        let dirhandle

        let chooseSaveDir = async function(){
            dirhandle = await window.showDirectoryPicker()
            $('#savedir').innerText = dirhandle.name
        }

        $('#dirbtn').addEventListener('click', chooseSaveDir)

        $('#savebtn').addEventListener('click', async function(){
            if (imageBlob === undefined || roimetadata === undefined) {
                alert('crop image first')
                return
            }
            let basename = $("#savebasename").value.trim()
            if (basename === "") {
                $("#savebasename").focus()
                return
            }
            if (dirhandle === undefined) {
                await chooseSaveDir()
            }
            let filehandle = await dirhandle.getFileHandle(basename + '.png', {create: true})
            let stream = await filehandle.createWritable()
            await stream.truncate(0)
            await stream.write(imageBlob)
            stream.close()
            filehandle = await dirhandle.getFileHandle(basename + '.roi.json', {create: true})
            stream = await filehandle.createWritable()
            await stream.truncate(0)
            await stream.write(roimetadata)
            stream.close()
            alert('saved')
        })

        let preventDefaults = e => {
            e.preventDefault()
            e.stopPropagation()
        }

        ;['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
            document.addEventListener(eventName, preventDefaults, false)
        })

        document.addEventListener('drop', function(e) {
            let dt = e.dataTransfer
            let files = dt.files
            console.log(files)
            if (files.length >= 1) {
                loadImage(files[0])
            }
        }, false)
    </script>
</body>

</html>
